<?php

/*
 * This file is part of the overtrue/wechat.
 *
 * (c) overtrue <i@overtrue.me>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace EasyTiktok\Kernel;

use EasyTiktok\Kernel\Exceptions\BadRequestException;
use EasyTiktok\Kernel\Traits\Observable;
use EasyTiktok\Kernel\Traits\ResponseCastable;

/**
 * Class ServerGuard.
 *
 * 1. url 里的 signature 只是将 token+nonce+timestamp 得到的签名，只是用于验证当前请求的，在公众号环境下一直有
 * 2. 企业号消息发送时是没有的，因为固定为完全模式，所以 url 里不会存在 signature, 只有 msg_signature 用于解密消息的
 *
 * @author overtrue <i@overtrue.me>
 */
class ServerGuard {
    use Observable;
    use ResponseCastable;

    /**
     * @var bool
     */
    protected bool $alwaysValidate = false;

    /**
     * @var ServiceContainer
     */
    protected ServiceContainer $app;

    /**
     * Constructor.
     *
     * @codeCoverageIgnore
     *
     * @param ServiceContainer $app
     */
    public function __construct(ServiceContainer $app) {
        $this->app = $app;

        foreach ($this->app->extension->observers() as $observer) {
            call_user_func_array([$this, 'push'], $observer);
        }
    }

    /**
     * @return $this
     *
     * @throws BadRequestException
     */
    public function validate(): ServerGuard {
        if (!$this->alwaysValidate && !$this->isSafeMode()) {
            return $this;
        }

        if ($this->app['request']->get('signature') !== $this->signature([
                $this->getToken(),
                $this->app['request']->get('timestamp'),
                $this->app['request']->get('nonce'),
            ])) {
            throw new BadRequestException('Invalid request signature.', 400);
        }

        return $this;
    }

    /**
     * Force validate request.
     *
     * @return $this
     */
    public function forceValidate(): ServerGuard {
        $this->alwaysValidate = true;

        return $this;
    }

    /**
     * @return string|null
     */
    protected function getToken() {
        return $this->app['config']['token'];
    }

    /**
     * @param array $params
     *
     * @return string
     */
    protected function signature(array $params): string {
        sort($params, SORT_STRING);

        return sha1(implode($params));
    }

    /**
     * Parse message array from raw php input.
     *
     * @param string $content
     *
     * @return array
     *
     * @throws BadRequestException
     */
    protected function parseMessage(string $content): array {
        try {
            if (0 === stripos($content, '<')) {
                $content = '';
            } else {
                // Handle JSON format.
                $dataSet = json_decode($content, true);
                if ($dataSet && (JSON_ERROR_NONE === json_last_error())) {
                    $content = $dataSet;
                }
            }

            return (array)$content;
        } catch (\Exception $e) {
            throw new BadRequestException(sprintf('Invalid message content:(%s) %s', $e->getCode(), $e->getMessage()), $e->getCode());
        }
    }

    /**
     * Check the request message safe mode.
     *
     * @return bool
     */
    protected function isSafeMode(): bool {
        return $this->app['request']->get('signature') && 'aes' === $this->app['request']->get('encrypt_type');
    }

    /**
     * @return bool
     */
    protected function shouldReturnRawResponse(): bool {
        return false;
    }

    /**
     * @param array $message
     *
     * @return mixed
     */
    protected function decryptMessage(array $message) {
        return $message = $this->app['encryptor']->decrypt(
            $message['Encrypt'],
            $this->app['request']->get('msg_signature'),
            $this->app['request']->get('nonce'),
            $this->app['request']->get('timestamp')
        );
    }
}
